<template>
  <svg width="500" height="500" class="container" ref="container" @click="click">
    <link-line v-for="item in lines" :id="item.id" :head="item.head" :tail="item.tail" :key="'line' + item.id" :nodeIds="item.nodeIds" ref="lines"></link-line>
    <node v-for="item in nodes" :id="item.id" :x="item.x" :y="item.y" :text="item.text" :key="'node' + item.id" ref="nodes"></node>
    <select-rect ref="selectRect"></select-rect>
  </svg>
</template>

<script>
import Node from '@/components/Node'
import LinkLine from '@/components/Line'
import SelectRect from '@/components/SelectRect'
let startId = 1

export default {
  components: {
    Node,
    LinkLine,
    SelectRect
  },
  props: {
  },
  data () {
    return {
      nodes: [], // 节点数据
      lines: [], // 线数据
      selectRect: { x: 0, y: 0, width: 100, height: 100 }, // 选择框数据
      selectedComps: [], // 选中的组件集合
      histories: [], // 历史记录
      historyIndex: -1 // 移动历史记录的序号
    }
  },
  methods: {
    getNode (id) {
      return this.nodes.find(item => item.id === id)
    },
    /**
     * 获取所有节点
     * @return   {Array} 数组
     * @Author   Jiangzhiwei
     * @datetime 2021-10-20T17:17:31+0800
     */
    getNodes () {
      return this.nodes
    },
    /**
     * 新增一个节点
     * @param    {Object} node 数据
     * @param    {Boolean} record 是否记录历史
     * @Author   Jiangzhiwei
     * @datetime 2021-10-20T17:16:25+0800
     */
    addNode (node, record) {
      if (!node.id) {
        node.id = this.getId()
      }
      this.nodes.push(node)
      if (record) {
        this.addHistory({
          type: 'addNode',
          data: node
        })
      }
      return node.id
    },
    /**
     * 移除一个节点
     * @param    {Integer} id 唯一标识
     * @Author   Jiangzhiwei
     * @datetime 2021-10-20T17:16:50+0800
     */
    removeNode (id) {
      const index = this.nodes.findIndex(item => item.id === id)
      if (index > -1) {
        this.nodes.splice(index, 1)
      }
    },
    /**
     * 获得连线
     * @param    {Integer} id 唯一标识
     * @return   {Object} 数据
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:12:26+0800
     */
    getLine (id) {
      return this.lines.find(item => item.id === id)
    },
    /**
     * 获得所有连线
     * @return   {Array} 数组
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:14:26+0800
     */
    getLines () {
      return this.lines
    },
    /**
     * 新增一条虚线
     * @param    {Object} line 虚线数据
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:14:57+0800
     */
    addLine (line) {
      if (!line.id) {
        line.id = this.getId()
      }
      this.lines.push(line)
      return line.id
    },
    /**
     * 删除线条
     * @param    {Integer} id 唯一标识
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:15:20+0800
     */
    removeLine (id) {
      const index = this.lines.findIndex(item => item.id === id)
      if (index > -1) {
        this.lines.splice(index, 1)
      }
    },
    /**
     * 获取node组件，没找到则返回null
     * @param    {Integer} id 唯一标识
     * @return   {Object} node组件
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:15:39+0800
     */
    getNodeComp (id) {
      return this.getNodeComps().find(item => item.id === id)
    },
    /**
     * 获取所有node组件
     * @return   {Array} node组件数组
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:16:37+0800
     */
    getNodeComps () {
      return this.$refs.nodes || []
    },
    /**
     * 获取line组件，没找到则返回null
     * @param    {Integer} id 唯一标识
     * @return   {Object} line组件
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:16:56+0800
     */
    getLineComp (id) {
      return this.getLineComps().find(item => item.id === id)
    },
    /**
     * 获取所有line组件
     * @return   {Array} line组件数组
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:17:19+0800
     */
    getLineComps () {
      return this.$refs.lines || []
    },
    /**
     * 移除组件（node/line）
     * @param    {Object} $comps 组件node/line
     * @param    {Boolean} record 是否记录历史
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:17:37+0800
     */
    removeComps ($comps, record) {
      if (record) {
        this.addHistory({
          type: 'removeComps',
          data: [].concat($comps)
        })
      }
      $comps.forEach(item => {
        item.remove()
      })
      this.cancelSelectComps()
    },
    /**
     * 根据节点id新增选中节点
     * @param    {Integer} nodeId node组件的id
     * @Author   Jiangzhiwei
     * @datetime 2021-10-21T16:44:02+0800
     */
    addSelectedLinesByNodeId (nodeId) {
      this.lines.forEach(item => {
        if (item.nodeIds.indexOf(',' + nodeId + ',') !== -1) { // 节点之一
          const line = this.$refs.lines.find(line => line.id === item.id)
          this.addSelectedComps(line)
        }
      })
    },
    /**
     * 新增选中
     * @param    {Object || Array} $comps 组件
     * @Author   Jiangzhiwei
     * @datetime 2021-10-21T16:37:32+0800
     */
    addSelectedComps ($comps) {
      if (!$comps) { // 未选中
        return
      }
      const arr = Array.isArray($comps) ? $comps : [$comps]
      arr.forEach(item => {
        item.setSelected()
        this.selectedComps.push(item)
      })
      this.selectedComps = Array.from(new Set(this.selectedComps))
    },
    /**
     * 选中
     * @param    {Object || Array} $comps 组件
     * @Author   Jiangzhiwei
     * @datetime 2021-10-21T14:16:22+0800
     */
    selectComps ($comps) {
      // 标记之前选中取消
      this.cancelSelectComps()
      // 标记当前选中
      this.addSelectedComps($comps)
    },
    /**
     * 取消选中
     * @param    {Object || Array} $comps 组件
     * @Author   Jiangzhiwei
     * @datetime 2021-10-21T13:31:48+0800
     */
    cancelSelectComps ($comps) {
      $comps = $comps || this.selectedComps // 默认全部取消
      const arr = Array.isArray($comps) ? $comps : [$comps]
      this.selectedComps.forEach(item => {
        if (arr.some(it => it.id === item.id)) { // 找到
          item.setSelected(false)
        }
      })
      this.selectedComps = this.selectedComps.filter(item => arr.every(it => item.id !== it.id))
    },
    /**
     * 单选
     * @param  {Object} $comp 组件
     * @param  {Boolean} pressShift 是否按下shift键
     * @author Jiangzhiwei
     * @time   2021-11-02T22:59:57+0800
     */
    singleSelect ($comp, pressShift) {
      if (pressShift) { // 如果按下shift
        if (this.hasSelected($comp)) { // 已选中
          this.cancelSelectComps($comp) // 取消选中
        } else { // 未选中
          this.addSelectedComps($comp) // 加选
        }
      } else { // 没有按shift键
        this.selectComps($comp) // 仅选
      }
    },
    /**
     * 多选（框选）
     * @param  {Object || Array} $comps 组件
     * @param  {Boolean} pressShift 是否按下shift键
     * @author Jiangzhiwei
     * @time   2021-11-02T22:45:30+0800
     */
    multiSelect ($comps, pressShift) {
      // console.log('multiSelect')
      if (pressShift) { // 如果按下shift
        this.addSelectedComps($comps)
      } else { // 未按下shift
        this.selectComps($comps)
      }
    },
    /**
     * 该组件是否已选中
     * @param  {Object} $comp vue组件
     * @return {Boolean} 是否选择
     * @author Jiangzhiwei
     * @time   2021-11-02T22:00:24+0800
     */
    hasSelected ($comp) {
      return this.selectedComps.some(item => item.id === $comp.id)
    },
    /**
     * 新增历史记录
     * @param    {Object} history 历史记录数据
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:19:50+0800
     */
    addHistory (history) {
      this.historyIndex++
      this.histories.length = this.historyIndex
      this.histories.push(history)
    },
    /**
     * 重做
     * @return   {Boolean} 是否进行了重做
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:20:06+0800
     */
    forwardHistory () {
      if (this.histories[this.historyIndex + 1]) {
        this.historyIndex++
        this.recoverHistory()
        console.log('重做')
        return true
      }
      return false
    },
    /**
     * 撤销
     * @return   {Boolean} 是否进行了撤销
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:21:12+0800
     */
    backwardHistory () {
      if (this.historyIndex > -1) {
        this.historyIndex--
        this.recoverHistory()
        console.log('撤销')
        return true
      }
      return false
    },
    /**
     * 恢复历史：移除所有组件，根据历史数据从第一步开始执行
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:21:32+0800
     */
    recoverHistory () {
      this.nodes = []
      this.lines = []
      this.selectedComps = []
      this.$nextTick(() => {
        for (var i = 0; i <= this.historyIndex; i++) {
          const history = this.histories[i]
          const data = history.data
          // console.log(history.type)
          if (history.type === 'addNode') { // 新增节点
            this.addNode(data)
          } else if (history.type === 'addLine') { // 新增连线
            const id = this.addLine(data)
            // 等线创建后再改为实线
            this.$nextTick(() => {
              const line = this.getLineComp(id)
              if (line) { // 如果线在后续行为中被删掉了，就找不到了
                line.setOver()
              }
            })
          } else if (history.type === 'removeComps') { // 删除节点/连线
            this.removeComps(data)
          } else if (history.type === 'moveNode') { // 移动节点
            // 等节点创建后再移动
            this.$nextTick(() => {
              const node = this.getNodeComp(data.id)
              if (node) { // 如果节点最后被删掉了，就找不到了
                node.refreshPosition(data.x, data.y)
              }
            })
          }
        }
      })
    },
    /**
     * 获取一个组件自增id
     * @return   {Integer} id
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:22:31+0800
     */
    getId () {
      return startId++
    },
    /**
     * 点击事件，取消所有选中状态
     * 取消选中状态动作放到mouseup事件中，此事件准备废弃
     * @param    {Object} event 事件
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:22:59+0800
     */
    click (event) {
      // this.cancelSelectComps()
    },
    /**
     * 快捷键
     * @param    {Object} event 事件
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:23:29+0800
     */
    keyup (event) {
      const key = event.key
      if (key === 'Delete' || key === 'Backspace') { // 删除
        this.removeComps(this.selectedComps, true)
      } else if (key === 'z' && event.ctrlKey) { // ctrl + z 撤销
        this.backwardHistory()
      } else if (key === 'y' && event.ctrlKey) { // ctrl + y 前进
        this.forwardHistory()
      }
    },
    /**
     * 按下准备框选
     * @param  {Object} event 事件
     * @author Jiangzhiwei
     * @time   2021-11-01T23:45:18+0800
     */
    mousedown (event) {
      const point = this.getPoint(event)
      this.$refs.selectRect.setStartPoint(point)
      // this.$refs.selectRect.setActived(true)
    },
    /**
     * 框选移动中
     * @param  {Object} event 事件
     * @author Jiangzhiwei
     * @time   2021-11-01T23:45:41+0800
     */
    mousemove (event) {
      if (this.$refs.selectRect.isStart) {
        const point = this.getPoint(event)
        this.$refs.selectRect.setFinishPoint(point) // isFinished = true
      }
    },
    /**
     * 框选结束
     * @param  {Object} event 事件
     * @author Jiangzhiwei
     * @time   2021-11-01T23:46:03+0800
     */
    mouseup (event) {
      if (this.$refs.selectRect.isFinished) { // 表示进行了框选
        const $comps = this.$refs.selectRect.getSelected()
        console.log($comps)
        this.multiSelect($comps, event.shiftKey)
      } else { // 没有框选
        this.cancelSelectComps()
      }
      if (this.$refs.selectRect.isStart) {
        this.$refs.selectRect.setOver()
      }
    },
    /**
     * 获取点在画布中的位置
     * @param  {Object} event 事件
     * @return {Object} 位置
     * @author Jiangzhiwei
     * @time   2021-11-01T23:46:28+0800
     */
    getPoint (event) {
      const rect = this.$refs.container.getBoundingClientRect()
      const x = event.clientX - rect.left
      const y = event.clientY - rect.top
      return { x, y }
    }
  },
  computed: {},
  mounted () {
    document.addEventListener('keyup', this.keyup)
    document.addEventListener('mousedown', this.mousedown)
    document.addEventListener('mousemove', this.mousemove)
    document.addEventListener('mouseup', this.mouseup)
  },
  destroy () {
    document.removeEventListener('keyup', this.keyup)
    document.removeEventListener('mousedown', this.mousedown)
    document.removeEventListener('mousemove', this.mousemove)
    document.removeEventListener('mouseup', this.mouseup)
  }
}
</script>

<style lang="scss">
  .container {
    border: 1px solid #999;
  }
</style>
